﻿/**
* @file        ut_coordinates.h
* @brief       封装坐标系相关运算
* @version     1.0
* @author      杜涛(贺亚猛整理)
* @date        2022.01.18
* @copyright   Copyright  2022 Langge Software Co., Ltd. All rights reserved.
*/

#ifndef UT_GIS_H
#define UT_GIS_H
#include <stdint.h>
#include <vector>
#include "ut_symbols.h"
#include "ut_point.h"
#include "ut_rect.h"

BEGIN_SPACE_LANGE_VDR
using TileID = uint64_t;
/**
 * @brief 封装坐标系相关运算
 * @author 杜涛(贺亚猛整理)
 * @date 2022.01.18
 */
class UT_API UTCoordinates
{
public:
    /**
     * @brief 投影点，描述各个投影系下点的坐标（单位：米）
     *        投影系包括: EPSG:900913
     *                   EPSG:4326
     *                   location
     */
    using ProjPoint=MD2DPOINT;
    /**
     * @brief 地球球面点，描述地球上点的坐标（单位：经纬度）
     */
    using LonLatPoint=MD2DPOINT;
    /**
     * @brief 像素点，描述瓦片或屏幕上的像素点（单位：像素）
     */
    using PixelPoint=MI2DPOINT;
    
    //using TileAnchor = TileID;
    using TileAnchor = struct _TileAnchor{
        uint32_t x = 0, y = 0;
    };
    #define GetTileAchorX(_Anchor_) (_Anchor_&0xFFFFFFF)
    #define GetTileAchorY(_Anchor_) ((_Anchor_>>28)&0xFFFFFFF)

    //#define SetTileAchorX(Anchor,x) (Anchor&(~0xFFFFFFF)|x)
    //#define SetTileAchorY(Anchor,y) (Anchor&(~(0xFFFFFFF<<28))|(y<<28))

    #define GetXformTileID GetTileAchorX
    #define GetYformTileID GetTileAchorY
    #define GetLevelformTileID(_id_) (_id_>>56)
        /**
         * @brief 将当前瓦片数据转为瓦片ID
         * @return 返回瓦片ID
         */
    static inline TileID MakeTileID(uint32_t x, uint32_t y ,uint32_t level) {
        TileID result = 0;
        result = (level&0xFF);
        result <<= 28;
        result |= (y&0xFFFFFFF);
        result <<= 28;
        result |= (x&0xFFFFFFF);
     
        return result;
    }
            /**
         * @brief 根据瓦片ID 获得瓦片参数信息
         * @param in_id - 瓦片ID
         * @param out_x - 返回瓦片X坐标
         * @param out_y - 返回瓦片Y坐标
         * @param out_levle - 返回瓦片所在级别
         */
    static inline void GetAnchorformTileID(TileID in_id, uint32_t & out_x,uint32_t & out_y,uint32_t & out_levle){
        out_x = (in_id & 0xFFFFFFF);
        out_y = ((in_id >> 28)&0xFFFFFFF);
        out_levle = in_id >> 56;
    }
        /**
         * @brief 根据瓦片ID 获得瓦片所在level
         * @param id - 瓦片ID
         * @return 返回瓦片所在level
         */
    static inline uint32_t GetLevelLayerformTileID(TileID id){
        return GetLevelformTileID(id);
    }    
    /**
     * @brief 瓦片ID
     */
   
//    using TileIDInfo = struct _TileIDInfo
    // {

    //     TileAnchor anchor;
    //     uint32_t level = 0;
    //     /**
    //      * @brief 将当前瓦片数据转为uint64_t数值
    //      * @return 返回转换后的数值
    //      */
    //     TileID toTileID();
    //     /**
    //      * @brief 通知指定的数值，为当前结构赋值
    //      * @param val - 数值
    //      */
    //     void fromTileID(const TileID & val);
    // };
    /**
     * @brief 常用值定义
     */
    static constexpr double EARTH_RADIUS=6378.137;                                // 地球半径（单位：公里）
    static constexpr double MIN_LAT=-85.05112878;                                 // 最小纬度
    static constexpr double MAX_LAT=85.05112878;                                  // 最大纬度
    static constexpr double MIN_LON=-180.0;                                       // 最小经度
    static constexpr double MAX_LON=180.0;                                        // 最大经度
    static constexpr double ORIGIN_SHIFT=2.0*UTMath::PI*EARTH_RADIUS*1000.0/2.0;          // 中心点位置
    static constexpr double INITIAL_RESOLUTION=(2.0*UTMath::PI*EARTH_RADIUS*1000)/256.0;  // 初始分辨率（含义：级别为0时，单位像素尺寸 单位：米）
    /**
     * @brief 投影系类型
     */
    using ProjType=enum class _ProjType
    {
        PROJ_TYPE_EPSG_900913,           // EPSG:900913
        PROJ_TYPE_EPSG_4326,             // EPSG:4326
        PROJ_TYPE_LOCAL,                 // 本地坐标（例如：北京54、西安80）
    };
public:
    /**
     * @brief 计算指定级别下瓦片尺寸（单位：像素）
     * @param level 级别
     * @return 返回计算的瓦片尺寸（单位：像素）
     */
    static uint64_t TileSize(const uint16_t level);
    /**
     * @brief  指定级别下，赤道处瓦片分辨率计算（单位：米）
     * @param  level   级别 
     * @return 返回瓦片分辨率（单位像素尺寸 单位：米）
     */
    static double TileResolution(const int32_t level);
    /**
     * @brief 将指定级别下的瓦片像素坐标转为投影坐标
     * @param x        x瓦片像素
     * @param y        y瓦片像素
     * @param level   级别
     * @param proj    投影系
     * @return 返回转换后的投影坐标
     */
    static ProjPoint TilePixelToProj(const double x,const double y,
        const int32_t level,const ProjType proj=ProjType::PROJ_TYPE_EPSG_900913);
    /**
     * @brief 将指定级别下的投影坐标转为瓦片像素坐标
     * @param x        x坐标
     * @param y        y坐标
     * @param level   级别
     * @param proj    投影系
     * @return 返回转换后的瓦片像素坐标
     */
    static PixelPoint ProjToTilePixel(const double x,const double y,
        const int32_t level,const ProjType proj=ProjType::PROJ_TYPE_EPSG_900913);
    /**
     * @brief 瓦片像素转瓦片标识
     * @param x   x坐标
     * @param y   y坐标
     * @return 返回转换后的瓦片标识
     */
    static TileAnchor TilePixelToTileAnchor(const double x, const double y);
    /**
     * @brief 将瓦片标识转为瓦片像素
     * @param x   瓦片x标识
     * @param y   瓦片y标识
     * @return 返回转换后的瓦片像素
     */
    static PixelPoint TileAnchorToTilePixel(const int32_t x,const int32_t y);
    /**
     * @brief 将经纬度转为投影坐标
     * @param lon   经度
     * @param lat   纬度
     * @param proj  投影系
     * @return 返回转换后的投影坐标
     */
    static ProjPoint LonLatToProj(const double lon,const double lat,
        const ProjType proj=ProjType::PROJ_TYPE_EPSG_900913);
    /**
     * @brief 将投影坐标转换为经纬度坐标
     * @param x      x坐标
     * @param y      y坐标
     * @param proj   投影系
     * @return 返回转换后的经纬度坐标
     */
    static LonLatPoint ProjToLonLat(const double x,const double y,
        const ProjType proj=ProjType::PROJ_TYPE_EPSG_900913);
    /**
     * @brief 计算两个点之间的距离
     * @param a - a投影点
     * @param b - b投影点
     * @return 返回两点之间的距离
     */
    static double DistanceProj(const LonLatPoint &a, const LonLatPoint &b);
    
    /**
     * @brief 计算经纬度之间的距离
     * 
     * @param a -经纬度
     * @param b -经纬度
     * @return 两点之间的距离 
     */
    static double DistanceMeters(const LonLatPoint &a, const LonLatPoint &b);
    /**
     * @brief 获取指定范围内的全部瓦片
     * @param s - 起始位置投影坐标
     * @param e - 结束位置投影坐标 
     * @param level - 级别
     * @return 返回范围内的瓦片 
     */
    static std::vector<uint64_t> GetTiles(const ProjPoint & s,
                                        const ProjPoint & e,
                                        const int32_t level);
    /**
     * @brief 将屏幕坐标(象素)转为投影坐标(米)
     * @param Point                屏幕坐标（单位：像素）
     * @param CenterProLonLat      中心点的投影坐标
     * @param ScreenHotXY          热点(地图旋转中心点)
     * @param fRes                 当前级别的分辨率
     * @param MaxExtent            全球范围-180 180 -90 90的投影坐标
     * @param fAngle               地图旋转角度
     * @param proj                 投影系(EPSG:900913, EPSG:4326, location(本地坐标,比如北京54和西安80等))
     * @return 投影坐标 
     */
    static MD2DPOINT ScreenToProj(const MF2DPOINT &Point,const MD2DPOINT &CenterProLonLat,
        const MI2DPOINT &ScreenHotXY,const float fRes, const MFUTRECT &MaxExtent,const float fAngle,
        const ProjType proj=ProjType::PROJ_TYPE_EPSG_900913);
    /**
     * @brief 将投影坐标(米)转为屏幕坐标(象素)
     * @param ProLonLat            托投影坐标
     * @param CenterProLonLat      中心点的投影坐标
     * @param ScreenHotXY          热点(地图旋转中心点)
     * @param fRes                 当前级别的分辨率
     * @param fAngle               地图旋转角度
     * @return 屏幕坐标 
     */
    static MF2DPOINT ProjToScreen(const MD2DPOINT &ProLonLat,const MD2DPOINT &CenterProLonLat,
        const MI2DPOINT &ScreenHotXY,const float fRes,const float fAngle);
    /**
     * @brief 将屏幕坐标(象素)转为球面坐标(经纬度)
     * @param Point                屏幕坐标
     * @param CenterProLonLat      中心点的投影坐标
     * @param ScreenHotXY          热点(地图旋转中心点)
     * @param fRes                 当前级别的分辨率
     * @param MaxExtent            全球范围-180 180 -90 90的投影坐标
     * @param fAngle               地图旋转角度
     * @param proj                 投影系(EPSG:900913, EPSG:4326, location(本地坐标,比如北京54和西安80等))
     * @return 球面坐标 
     */
    static MF2DPOINT ScreenToLonLat(const MF2DPOINT &Point,const MD2DPOINT &CenterProLonLat,
        const MI2DPOINT &ScreenHotXY,const float fRes,const MFUTRECT &MaxExtent,
        const float fAngle,const ProjType proj=ProjType::PROJ_TYPE_EPSG_900913);
    /**
     * @brief 将球面坐标(经纬度)转为屏幕坐标(象素)
     * @param LonLat                 球面坐标
     * @param CenterProLonLat        中心点的投影坐标
     * @param ScreenHotXY            热点(地图旋转中心点)
     * @param fRes                   当前级别的分辨率
     * @param fAngle                 地图旋转角度
     * @param proj                   投影系(EPSG:900913, EPSG:4326, location(本地坐标,比如北京54和西安80等))
     * @return 屏幕坐标 
     */
    static MF2DPOINT LonLatToScreen(const MD2DPOINT &LonLat,const MD2DPOINT &CenterProLonLat,
        const MI2DPOINT &ScreenHotXY,const float fRes,const float fAngle,
        const ProjType proj=ProjType::PROJ_TYPE_EPSG_900913);
private:
    UTCoordinates()=delete;
    UTCoordinates(UTCoordinates &&)=delete;
    UTCoordinates(const UTCoordinates &)=delete;
    UTCoordinates &operator=(UTCoordinates &&)=delete;
    UTCoordinates &operator=(const UTCoordinates &)=delete;
};

END_SPACE_LANGE_VDR

#endif // !UT_GIS_H
